/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* $Id: base64.c 16 2007-06-10 02:10:04Z kf701.ye $ */

#include <sys/types.h>
#include <fcntl.h>

#include "kf701.h"

/**
 * @brief base64 encode
 *
 * Note: dest must be at least 4/3 length of src
 *
 * @param   dest  encoded buffer
 * @param   src   raw buffer
 * @param   size  length of src
 * @return  the actual data length in dest
 */
int32_t encode_b64(uint8_t* dest,const uint8_t* src,uint32_t size)
{
	int i, tmp = 0, b64_tmp = 0;

	if ( dest == NULL || src == NULL )
		return -1;

	while ((size - b64_tmp) >= 3) {
		// every 3 bytes of source change to 4 bytes of destination, 4*6 = 3*8
		dest[tmp] = 0x3F & (src[b64_tmp]>>2);
		dest[tmp+1] = ((src[b64_tmp]<<4) & 0x30) | ((src[b64_tmp+1]>>4) & 0x0F);
		dest[tmp+2] = ((src[b64_tmp+1]<<2) & 0x3C) | ((src[b64_tmp+2]>>6) & 0x03);
		dest[tmp+3] = 0x3F & src[b64_tmp+2];
		for (i=0; i<=3; i++) {
			if ( (dest[tmp+i] <= 25) )
				dest[tmp+i] += 'A';
			else	
				if (dest[tmp+i] <= 51)
					dest[tmp+i] += 'a' - 26;	       
				else
					if (dest[tmp+i] <= 61)
						dest[tmp+i] += '0' - 52;
			if (dest[tmp+i] == 62)
				dest[tmp+i] = '+';
			if (dest[tmp+i] == 63)
				dest[tmp+i] = '/';
		}

		tmp += 4;
		b64_tmp += 3;
	} //end while	
	if (b64_tmp == size) {
		dest[tmp] = '\0';
		return tmp;
	}
	if ((size - b64_tmp) == 1) {    //one
		dest[tmp] = 0x3F & (src[b64_tmp]>>2);
		dest[tmp+1] = (src[b64_tmp]<<4) & 0x30;
		dest[tmp+2] = '=';
	}
	else {    //two
		dest[tmp] = 0x3F & (src[b64_tmp]>>2);
		dest[tmp+1] = ((src[b64_tmp]<<4) & 0x30) | ((src[b64_tmp+1]>>4) & 0x0F);
		dest[tmp+2] = (src[b64_tmp+1]<<2) & 0x3F;	
	}

	for (i=0; i<=(size - b64_tmp); i++) {
		if  (dest[tmp+i] <= 25)
			dest[tmp+i] += 'A';
		else	
			if (dest[tmp+i] <= 51)
				dest[tmp+i] += 'a' - 26;	       
			else
				if (dest[tmp+i] <= 61)
					dest[tmp+i] += '0' - 52;	//end if
		if (dest[tmp+i] == 62)
			dest[tmp+i] = '+';
		if (dest[tmp+i] == 63)
			dest[tmp+i] = '/';
	}

	dest[tmp+3] = '=';
	dest[tmp+4] = '\0';

	return tmp+4;
}

static const short base64_reverse_table[256] = {
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
	-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};

/**
 * @brief base64 decode
 *
 * Note: dest must be at least 3/4 length of src
 *
 * @param   dest  decoded buffer
 * @param   src   encoded buffer
 * @param   size  length of src
 * @return  the actual data length in dest
 */
int32_t decode_b64(uint8_t *dest,const uint8_t *src,uint32_t size)
{
	const uint8_t *current = src;
	int ch, i = 0, j = 0, k;
	/* this sucks for threaded environments */
	uint8_t *result;
	int ret_length;

	result = dest ;
	if (result == NULL) {
		return -1;
	}

	/* run through the whole string, converting as we go */
	while ((ch = *current++) != '\0' && size-- > 0) {
		if (ch == '=') break;

		/* When Base64 gets POSTed, all pluses are interpreted as spaces.
		   This line changes them back.  It's not exactly the Base64 spec,
		   but it is completely compatible with it (the spec says that
		   spaces are invalid).  This will also save many people considerable
		   headache.  - Turadg Aleahmad <turadg@wise.berkeley.edu>
		   */

		if (ch == ' ') ch = '+'; 

		ch = base64_reverse_table[ch];
		if (ch < 0) continue;

		switch(i % 4) {
			case 0:
				result[j] = ch << 2;
				break;
			case 1:
				result[j++] |= ch >> 4;
				result[j] = (ch & 0x0f) << 4;
				break;
			case 2:
				result[j++] |= ch >>2;
				result[j] = (ch & 0x03) << 6;
				break;
			case 3:
				result[j++] |= ch;
				break;
		}
		i++;
	}

	k = j;
	/* mop things up if we ended on a boundary */
	if (ch == '=' ) {
		switch(i % 4) {
			case 1:
				return -1;
			case 2:
				k++;
			case 3:
				result[k++] = 0;
		}
	}
	ret_length = j;
	result[j] = '\0';
	return ret_length;
}

/**
 * @brief   base64 encode a file to buffer
 * @param   b64_buf    encoded buffer
 * @param   file       raw file
 * @return  the actual data length in b64_buf
 */
int32_t b64_file_to_buf (uint8_t *b64_buf, const char *file)
{
	int r_size = 100*1024;
	int s_fd = open(file,O_RDONLY);
	uint8_t s_buff[100*1024];	
	uint8_t *ptr ;
	int count;

	if (s_fd < 0)
		return -1;
	if (NULL == s_buff) {
		close(s_fd);
		return -2;
	}
	ptr = b64_buf;
	do {
		int i;
		count = read(s_fd,s_buff,r_size);
		if (count < 0) {
			close(s_fd);
			return -3;
		}
		i = count;
		while (i >= 17*3) {
			encode_b64(ptr,&s_buff[count-i],17*4);
			ptr += 17*4;
			ptr += sprintf((char*)ptr,"\n");			
			i -= 17*3;
		}
		if (i > 0) {
			ptr += encode_b64(ptr,&s_buff[count-i],i);
			ptr += sprintf((char*)ptr,"\n");			
		}
		if (count < r_size)
			break;
	} while (count == r_size);
	close(s_fd);
	return (ptr-b64_buf);
}
